package ca.ericslandry.client.widget;

import ca.ericslandry.shared.Constants;
import ca.ericslandry.shared.util.GeoHash;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.control.LargeMapControl;
import com.google.gwt.maps.client.event.MapClickHandler;
import com.google.gwt.maps.client.event.MapDragHandler;
import com.google.gwt.maps.client.geocode.Geocoder;
import com.google.gwt.maps.client.geocode.LatLngCallback;
import com.google.gwt.maps.client.geocode.LocationCallback;
import com.google.gwt.maps.client.geocode.Placemark;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.maps.client.overlay.Marker;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class LocationPicker extends Composite implements HasValue<String> {
	private String geohashValue;
	private MapWidget map = new MapWidget();
	private LatLng oldCenter;
	private TextBox address = new TextBox();
	private HTML currentLocation = new HTML();
	private HTML currentCoordinates = new HTML();
	private DockPanel mainPanel = new DockPanel();
	/**
	 * Updates the location text
	 */
	private Timer timer = new Timer() {
		@Override
		public void run() {
			updateCurrentLocation();
		}
	};

	public LocationPicker() {
		initMap();
		updateCurrentCoordinates();
		updateCurrentLocation();
		timer.schedule(1000);
		timer.scheduleRepeating(5000);
		mainPanel.setWidth("100%");
		currentCoordinates.setStyleName(Constants.CSS.FOOTNOTE);
		mainPanel.add(createGuidePanel(), DockPanel.WEST);
		mainPanel.add(map, DockPanel.EAST);
		initWidget(mainPanel);
	}

	private void initMap() {
		LatLng center = LatLng.newInstance(45.4151, -75.7004);
		map.setCenter(center, 7);
		map.setPixelSize(400, 350);
		map.addControl(new LargeMapControl());
		
		map.addMapDragHandler(new MapDragHandler() {
			@Override
			public void onDrag(MapDragEvent event) {
				updateCurrentCoordinates();
			}
		});
		map.addMapClickHandler(new MapClickHandler() {
			@Override
			public void onClick(MapClickEvent event) {
				LatLng point = event.getLatLng();
				map.setCenter(point);
				updateCurrentLocation();
				updateCurrentCoordinates();
			}
		});
	}

	private Widget createGuidePanel() {
		VerticalPanel guidePanel = new VerticalPanel();
		guidePanel.setSpacing(10);
		guidePanel.setWidth("100%");
		currentLocation.setWidth("100%");
		guidePanel.add(currentLocation);
		Button go = new Button("Go");
		go.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				new Geocoder().getLatLng(address.getText(), new LatLngCallback() {
					@Override
					public void onFailure() {
						Window.alert("Can't find address.");
					}

					@Override
					public void onSuccess(LatLng point) {
						map.setCenter(point, 13);
						updateCurrentCoordinates();
					}
				});
			}
		});
		HorizontalPanel searchPanel = new HorizontalPanel();
		searchPanel.add(address);
		searchPanel.add(go);
		guidePanel.add(searchPanel);
		guidePanel.setVerticalAlignment(HorizontalPanel.ALIGN_BOTTOM);
		guidePanel.add(currentCoordinates);
		return guidePanel;
	}

	private void updateCurrentLocation() {
		LatLng center = map.getCenter();
		if (!center.equals(oldCenter)) {
			oldCenter = center;
			new Geocoder().getLocations(center, new LocationCallback() {
				@Override
				public void onSuccess(JsArray<Placemark> locations) {
					currentLocation.setHTML("<p>You're looking at: <b>" + locations.get(0).getAddress()
							+ "</b>.</p><p>Try dragging the map around or searching for an address or a postal code.</p>");
				}

				@Override
				public void onFailure(int statusCode) {
					// TODO: Show exclamation mark next to addr text box
					Log.error("Geocoding failed with statusCode: " + statusCode);
				}
			});
		}
	}

	private void updateCurrentCoordinates() {
		LatLng center = map.getCenter();
		GeoHash geohash = GeoHash.fromLatLng(center);
		geohashValue = geohash.toBase32();
		NumberFormat fmt = NumberFormat.getFormat("###.######");
		double latitude = center.getLatitude();
		double longitude = center.getLongitude();
		currentCoordinates.setHTML("Latitude: " + fmt.format(latitude) + "<br/>Longitude: " + fmt.format(longitude)
				+ "<br/>Geohash: " + geohashValue);
		Marker marker = new Marker(center);
		map.clearOverlays();
		map.addOverlay(marker);
	}

	@Override
	public String getValue() {
		return geohashValue;
	}

	@Override
	public void setValue(String value) {
		setValue(value, false);
	}

	@Override
	public void setValue(String value, boolean fireEvents) {
		String oldGeohash = geohashValue;
		geohashValue = value;
		if (fireEvents) {
			ValueChangeEvent.fireIfNotEqual(this, oldGeohash, value);
		}
	}

	@Override
	public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) {
		return null;
	}
}
